package com.aforce3.trailertracer.utils;

import java.util.ArrayList;

import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import com.aforce3.trailertracer.Material;
import com.aforce3.trailertracer.Ray;
import com.aforce3.trailertracer.scenes.PointLight;
import com.aforce3.trailertracer.scenes.Primitive;
import com.aforce3.trailertracer.scenes.Scene;
import com.aforce3.trailertracer.scenes.Shape;

public class ColorModifier
{
	public static final Color3f BLACK = new Color3f(0,0,0);
	
	public static int multiplyColor(Color3f a, Color3f b)
	{
		return 0xFF000000 + ((int) (a.x * b.x * 0x00FF0000)) & 0x00FF0000 + ((int) (a.y * b.y * 0x0000FF00)) & 0x0000FF00 + ((int) (a.z * b.z * 0x000000FF)) & 0x000000FF;
	}

	public static int toInt(Color3f a)
	{
		return 0xFF000000 + (int) (((int)(a.x * 0x00FF0000) & 0x00FF0000) + ((int)(a.y * 0x0000FF00) & 0x0000FF00) + ((int)(a.z * 0x000000FF) & 0x000000FF));
	}

	public static Color3f toColor3f(int rgb)
	{
		float a = (float) (((rgb & 0x00FF0000) >> 16) / 255.0);
		float b = (float) (((rgb & 0x0000FF00) >> 8) / 255.0);
		float c = (float) (((rgb & 0x000000FF)) / 255.0);

		return new Color3f(a, b, c);
	}
	
	public static Color3f merge(double weight, Color3f initial, Color3f discovered)
	{
		// Assertions
		if(initial.x > 1) throw new RuntimeException("Impossible value"+initial.x);
		if(initial.y > 1) throw new RuntimeException("Impossible value"+initial.y);
		if(initial.z > 1) throw new RuntimeException("Impossible value"+initial.z);
		if(discovered.x > 1) throw new RuntimeException("Impossible value");
		if(discovered.y > 1) throw new RuntimeException("Impossible value");
		if(discovered.z > 1) throw new RuntimeException("Impossible value");
		
		double red = (initial.x*weight)/(weight+1)+discovered.x/(weight+1);
		double green = (initial.y*weight)/(weight+1)+discovered.y/(weight+1);
		double blue = (initial.z*weight)/(weight+1)+discovered.z/(weight+1);

		// More assertions
		if(red > 1 && red-1 > 0.000000001)  throw new RuntimeException("Impossible value "+red);
		if(green > 1 && green-1 > 0.000000001)  throw new RuntimeException("Impossible value "+green);
		if(blue > 1 && blue-1 > 0.000000001)  throw new RuntimeException("Impossible value "+blue);
		
		// Necessary due to potential round off error
		if(red > 1)  red = 1;
		if(green > 1) green = 1;
		if(blue > 1) blue = 1;
		
		return new Color3f((float)red, (float)green, (float)blue);
	}
	
	/** Determines the material's color (not including reflections or refractions.  Looks at all the light sources. **/
	public static Color3f calculateLight(Scene root, Primitive self, Ray incomingRay, Point3d intersection){
		Material material = self.getMaterial();
		ArrayList<PointLight> lights = root.getLights();
		
		Ray ray;
		//float distance;
		//float intensity;
		Color c = new Color();
		
		for (PointLight light : lights){
			//find if light is blocked
			Vector3d direction = new Vector3d(light.getCenter());
			direction.sub(intersection);
			//System.out.println(direction);
			
			direction.normalize();
			ray = new Ray(intersection, direction, 2);
			
			//System.out.println("searching light");
			Scene closest = root.getClosest(ray, self);
			if (light == closest){
				float distance = (float) intersection.distance(light.getCenter());
				if (distance < 0.01){
					System.out.println(distance);
				}
				//System.out.println("ambient");
				//System.out.println(distance);
				
				
				Color pColor = getDiffuseColor(self, ray, incomingRay, light);
				c.add(pColor);
				
				//specular flag calculates specular light - not needed for refraction
				//if(specularFlag){
					Color sColor = getSpecularColor(self, ray, incomingRay, light);
					c.add(sColor);
				//}
					
					
				/*
				//float scale = 1f / distance;
				if (scale > 0 && scale < 1){
					pColor.scale(scale);
					c.add(pColor);
				}else if(scale > 1){
					//System.out.println(scale);
					c.add(pColor);
				}
				*/
			}
		}
		
		c.add(self.getMaterial().getAmbientColor());
		c.add(self.getMaterial().getEmissiveColor());
		
		c.clamp(0, 1);
		
		return c;
	}
	
	public static Color getDiffuseColor(Primitive self, Ray ray, Ray incomingRay, PointLight l){
		Vector3d normal = self.getNormal(incomingRay);
		Color c = new Color(self.getMaterial().getDiffuseColor());
		Color3f e = l.getMaterial().getEmissiveColor();
		e.scale(l.getIntensity());
		
		//normal.scale(-1);
		float dotVal = (float) normal.dot(ray.getDirection());
		
		if (dotVal > 0){
			e.scale(dotVal);
			c.add(e);
		}else{
			e.scale(0);
		}
		
		//scale for the rest
		//c.scale(0.2f);
		//System.out.println(c);
		
		c.clamp(0, 1);
		return c;
	}
	
	public static Color getSpecularColor(Primitive self, Ray ray, Ray incomingRay, PointLight l){
		Vector3d normal = self.getNormal(incomingRay);
		float sConstant = self.getMaterial().getShininess();
		//reflects its own specular color
		//Color s = new Color(self.getMaterial().getSpecularColor());
		//reflects the light's specular color
		Color s = new Color(l.getMaterial().getSpecularColor());
		
		//ensure the ray is bouncing towards the light
		if(normal.dot(ray.getDirection()) < 0){
			return new Color();
		}
		Ray rRay = self.calculateReflection(incomingRay);
		Vector3d outGoing = rRay.getDirection();
		
		float dotVal = (float) outGoing.dot(ray.getDirection());
		
		if (dotVal > 0){
			
			dotVal = (float) Math.pow(dotVal, sConstant);
			s.scale(dotVal);
			
			//return new Color(1, 1, 1);
			
			s.clamp(0, 1);
			return s;
			
		}else{
			return new Color();
		}
	}
	
	
	public static Color3f calculateRefractedPoint(Scene root, Shape self, Ray outGoingRay){
		Point3d intersection = outGoingRay.getSource();
		
		Material material = self.getMaterial();
		ArrayList<PointLight> lights = root.getLights();
		
		Ray ray;
		Color c = new Color();
		c.add(material.getAmbientColor());
		
		for (PointLight light : lights){
			//find if light is blocked
			Vector3d direction = new Vector3d(light.getCenter());
			direction.sub(intersection);
			//System.out.println(direction);
			
			direction.normalize();
			ray = new Ray(intersection, direction, 2);
			
			//System.out.println("searching light");
			Scene closest = root.getClosest(ray, self);
			if (light == closest){
				float distance = (float) intersection.distance(light.getCenter());
				if (distance < 0.01){
					System.out.println(distance);
				}
				
				Color pColor = getDiffuseColor(self, ray, intersection, light);
				c.add(pColor);
				
				Color sColor = getRefractedSpecularColor(self, ray, intersection, outGoingRay, light);
				c.add(sColor);
			}
		}
		c.add(self.getMaterial().getEmissiveColor());
		
		c.clamp(0, 1);
		
		return c;
	}
	
	public static Color getDiffuseColor(Shape self, Ray ray, Point3d intersection, PointLight l){
		Vector3d normal = self.getNormal(intersection);
		Color c = new Color(self.getMaterial().getDiffuseColor());
		Color3f e = l.getMaterial().getEmissiveColor();
		e.scale(l.getIntensity());
		
		float dotVal = (float) normal.dot(ray.getDirection());
		
		if (dotVal > 0){
			e.scale(dotVal);
			c.add(e);
		}else{
			e.scale(0);
		}
		
		c.clamp(0, 1);
		return c;
	}
	
	public static Color getRefractedSpecularColor(Shape self, Ray ray, Point3d intersection, Ray outGoingRay, PointLight l){
		Vector3d normal = self.getNormal(intersection);
		float sConstant = self.getMaterial().getShininess();
		
		//Color s = new Color(self.getMaterial().getSpecularColor());
		//reflects the light's specular color
		Color s = new Color(l.getMaterial().getSpecularColor());
		
		
		//ensure the ray is bouncing towards the light
		if(normal.dot(ray.getDirection()) < 0){
			return new Color();
		}
		
		Vector3d outGoing = outGoingRay.getDirection();
		
		float dotVal = (float) outGoing.dot(ray.getDirection());
		
		if (dotVal > 0){
			
			dotVal = (float) Math.pow(dotVal, sConstant);
			s.scale(dotVal);
			
			s.clamp(0, 1);
			
			//return s;
			return s;
		}else{
			return new Color();
		}
	}
	
}
